写给声音设计师的极速 GUI 开发大法(三)实例:WAAPI Code Generator 与 WAAPI Database

大家好,我是溪夜。
在《写给声音设计师的极速 GUI 开发大法》第三篇中,会详解两个实际项目中的 UI 设计。
其一是我的个人项目 WAAPI Code Generator,其二是我和光子工作室的技术音频李AA老师合作的项目 WAAPI Database。
作为两个实用级的 WAAPI 项目,虽然不算什么复杂的设计,但可以通过对它们让大家了解 PySimpleGUI 在实际开发中的应用方法。出于我一贯的讲解习惯,本文会尽量对除 Python 基础逻辑之外的每一行代码都进行注释,希望通过这样解答大家所有可能存在的疑惑。

本文目录:

[toc]

WAAPI Code Generator 概述

1.1. 概述

在系列文章《人人都能用 WAAPI》的实例篇中,WAAPI Code Generator 作为一个提高 WAAPI 使用效率的工具介绍给了大家。
它的主要目的是为了优化 WAAPI 调用时繁琐的 JSON 格式配置构建,把这一部分繁琐的操作抽离出来,彻底把 WAAPI 的使用参数化。

1.2. 技术栈

WAAPI Code Generator 全部代码均通过 Python 完成,使用了两个库:

  1. 负调用 WAAPI 的 waapi-client
  2. 负责 GUI 实现的 PySimpleGUI

本文将会着重讲解程序中 GUI 设计的部分,故有关 WAAPI 使用的代码不会讲解。
在 GitHub 仓库中 WAAPI Code Generator 的所有代码都有完善的注释和 Docstrings,有兴趣的朋友欢迎自行查阅。
如果认可我的程序,请不要在 GitHub 吝啬您的 Star!

1.3. GUI 原型设计

在设计 WAAPI Code Generator 的布局时,我面临了很多挑战。
出于小工具体量的考量,我为其设计了从左至右、从上至下的“瀑布”信号流逻辑,颇有录音棚中机架信号流的使用风格。符合 “Don’t make me think” 的 UX 设计原则,用户只需要从左至右按照自然理解顺序逐块调用功能即可。

1.3.1. 第一栏

第一栏中包括 WAMP 连接模块、API 分类选择模块、工程浏览模块三部分。
其中 WAMP 连接模块和 API 分类模块的实现都非常简单,不值得一提。
工程浏览模块实现起来稍复杂,PySimpleGUI 的 Tree 元素所需的 TreeData 数据格式是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义根节点
: root
# 每个缩进代表更高一级的目录。其中每行 : 的前面为这行 Tree 元素储存的内容,可以为任何值,: 后面为这行在 Tree 浏览器中显示出的内容
/Users/xiye/Downloads/Treetest/.DS_Store : .DS_Store
/Users/xiye/Downloads/Treetest/Folder2 : Folder2
/Users/xiye/Downloads/Treetest/Folder3 : Folder3
# 第二个缩进,缩进中为 Folder3 的子节点
/Users/xiye/Downloads/Treetest/Folder3/.DS_Store : .DS_Store
/Users/xiye/Downloads/Treetest/Folder3/Filer1.jpg : Filer1.jpg
/Users/xiye/Downloads/Treetest/Folder3/Folder11 : Folder11
# 第三个缩进,缩进中为 Folder11 的子节点
/Users/xiye/Downloads/Treetest/Folder3/Folder11/Filer111.jpg : Filer111.jpg
/Users/xiye/Downloads/Treetest/Filer1.jpg : Filer1.jpg
/Users/xiye/Downloads/Treetest/Folder1 : Folder1
/Users/xiye/Downloads/Treetest/Folder1/.DS_Store : .DS_Store
/Users/xiye/Downloads/Treetest/Folder1/Filer1.jpg : Filer1.jpg
/Users/xiye/Downloads/Treetest/Folder1/Folder11 : Folder11
/Users/xiye/Downloads/Treetest/Folder1/Folder11/Filer111.jpg : Filer111.jpg

WAAPI 中如果想要获取工程所有标签页内的每一项内容,需要用 ak.wwise.core.object.get 进行大量的调用,既麻烦又低效率。
因此在这部分我设计了一种获取算法,直接读取工程文件夹内的 .wwu 文件,从其中直接获取整套工程的架构(包括层级与所有的 Name、GUID、Short ID)。

1.3.2. 第二栏

PySimpleGUI(包括 Qt 版的 PySimpleGUIQt)不能实现类似原生 Qt 中的 Tree 元素嵌套 ComboBox 元素(即 TreeComboBox,树状列表中的节点可变为下拉选择框)的自定义控件,所以在这部分我使用了一种变通的方法来解决这个问题。
首先根据使用情况分析可能用到的元素数(选用参数尽可能多的 API 进行分析):

  • ak.wwise.core.object.get 最多需要五个参数。必备参数有:form 为查询的起点(必备参数,可用 ComboBox)加具体参数(可用 InputText)。可选参数有:transform 为对 form 参数的变换,可以指定多项(可用 ComboBox)及其值(可用 InputText)。options 中分别是 platform 查询平台(可用 ComboBox),return 返回值(可用多选框),language 平台语言(可用 ComboBox)。
  • ak.wwise.core.object.create 最多需要八个的参数。必备参数有:name 为新创建的对象名(可用 InputText),parent 为父对象的信息(可用 InputText),children 为子对象信息(可能涉及多层嵌套,比较复杂),type 为对象类型(可用 ComboBox)。可选参数有:platform 为创建对象所在的平台(可用 ComboBox),notes 为备注(可用 InputText), autoAddToSourceControl 为版本控制添加选项(可用 ComboBox),onNameConflict 为名称冲突解决方案(可用 ComboBox)。

可以看到参数中除了有选项型或输入型参数外,还会有 children 这种需用户自行决定层级深度的结构。
为了把这种参数模式抽象出来,我设计了一种变通的方法,即把当前 API 所需要的所有属性放在 Tree 元素中,点击每个属性名即可在右边设置它的属性。
【配图】
如上图所示,其设计原则为:

  1. 对于普通的属性来说,无论它是选项型还是输入型参数,它获取的属性指派会以子节点的形式显示在当前属性节点的下方。
  2. 对于 children 这种树结构,它在 Tree 中会直接显示节点的信息,而工程架构依然以树形显示。此外在属性设置页面会增加 Add、Remove 两个按钮,以实现树中节点的创建。

1.3.3. 第三栏

第三栏很简单,是代码的生成区。
我对默认生成的代码设计了自动复制到剪贴板的逻辑,并且提供了单独拷贝参数与函数调用的语句,能够应对用户不同情况下的需求。

1.4. GUI 代码实现

自定义主题,以获得跟 Wwise 黑暗模式一样的 GUI 融合:

1
2
3
4
5
sg.LOOK_AND_FEEL_TABLE['Wwise_Like_Theme'] = {  'BACKGROUND': '#5b5b5b', 'TEXT': '#e2e2e2', 'INPUT': '#404040',
'SCROLL': '#565656', 'TEXT_INPUT': '#e2e2e2', 'BUTTON': ('#e2e2e2', '#6f6f6f'),
'PROGRESS': "#4f4f4f", 'SCROLL': '#868686', 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0 }

sg.theme('Wwise_Like_Theme')

主布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
############################################### First Column ###############################################

waapi_setting_frame = [ [sg.Text('IP'), sg.InputText('127.0.0.1', size=(12, 1)), sg.Text(' '), sg.Text('Port'), sg.InputText('8080', size=(5, 1))],
[sg.Text('WAAPI Status'), sg.InputText(size=(18, 1))],
[sg.Button('Connect', size=(10,1)), sg.Button('Disconnect', size=(10,1))] ]

path_or_id_column = [ [sg.Radio('Path', "RADIO1", default=True)],
[sg.Radio('GUID', "RADIO1")],
[sg.Radio('Short ID', "RADIO1")] ]

copy_button_column = [ [sg.Button('Copy', size=(18,1))],
[sg.Button('Add', size=(8,1)), sg.Button('Remove', size=(8,1))] ]

project_browser_layout = [ [sg.Frame('Make sure WAAPI set correctly', waapi_setting_frame, element_justification='c', border_width=3)],
[sg.Text('Then choose a WAAPI class')],
[sg.Combo(('e.g. ak.wwise.waapi'), size=(31,1))],
[sg.Text('WAAPI List')],
[sg.Listbox(values=('ak.wwise.waapi.getFunctions', 'ak.wwise.waapi.getSchema', 'ak.wwise.waapi.getTopics'), size=(31,15))],
[sg.Text('_' * 35)],
[sg.Text('Maybe you need sone path ID stuff?')],
[sg.Combo(('e.g. Events or SoundBanks etc.'), size=(31,1))],
[sg.Text('Project Tree')],
[sg.Listbox(values=('Should be tree', 'element here'), size=(31, 15))],
[sg.Column(path_or_id_column), sg.Column(copy_button_column, element_justification='c')] ]

############################################### Second Column ###############################################

args_decision_column = [[sg.Text('Args Decision')],
[sg.Listbox(values=('From Type', 'From', 'etc.'), size=(30,23))]]

args_document_column = [[sg.Text('Args Document')],
[sg.Listbox(values=('Some shit'), size=(30,23))]]

opts_decision_column = [[sg.Text('Opts Decision')],
[sg.Listbox(values=('Platform', 'Return'), size=(30,23))]]

opts_document_column = [[sg.Text('Opts Document')],
[sg.Listbox(values=('Some shit'), size=(30,23))]]

program_language_column = [ [sg.Radio('Python', "RADIO2", default=True), sg.Radio('C#', "RADIO2")],
[sg.Radio('C++', "RADIO2"), sg.Text(' '), sg.Radio('JavaScript', "RADIO2")] ]

args_opts_layout = [[sg.Column(args_decision_column), sg.Column(args_document_column)],
[sg.Column(opts_decision_column), sg.Column(opts_document_column)],
[sg.Column(program_language_column), sg.Button('Generate', size=(10,1))]]

############################################### Third Column ###############################################

code_generated_layout = [ [sg.Text('Code Result')],
[sg.Listbox(values=('Platform', 'Return'), size=(30, 48))],
[sg.Text('Copy what you want')],
[sg.Button('Args', size=(5,1)), sg.Button('Opts', size=(5,1)), sg.Button('Functions', size=(9,1))] ]

############################################### Main Layout ###############################################

main_layout = [ [sg.Column(project_browser_layout), sg.VerticalSeparator(pad=None), sg.Column(args_opts_layout, element_justification='c'),
sg.VerticalSeparator(pad=None), sg.Column(code_generated_layout)] ]

window = sg.Window('WAAPI Code Generator', main_layout, font=("Helvetica", 12))

Event Loop:

1
2
3
4
5
6
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'Cancel':
break

window.close()

WAAPI Database 概述

2.1. 概述

WAAPI Database 的主要功能是在 Wwise 生成 SoundBank 后抓取工程中的信息并存入数据库中,数据库中的内容可供分析之用。
在 GUI 设计时,我为 WAAPI Database 增加了手工抓取的功能,这样不用生成 SoundBank 也可以随时进行数据抓取。还实现了一个简易的 SQLite Viewer,可以让用户在程序内直接进行数据库内数据的检视,并可实现 CSV、XLSX 格式的导出,以方便用户有数据导出的需求。

2.2. 技术栈

WAAPI Database 全部代码均通过 Python 完成,使用了三个库:

  1. 负责数据库的 SQLite
  2. 负责 GUI 实现的 PySimpleGUI
  3. 负责调用 WAAPI 的 waapi-client

WAAPI Database 还有一个在 UE4 内运行版本,此版本暂时不考虑开源。
本文将会着重讲解我在这个程序中 GUI 设计的部分,故有关 WAAPI 和 SQLite 使用的代码不会进行分析。如果对 WAAPI 部分的代码有兴趣,可以在《人人都能用 WAAPI(四)Beyond WAAPI》文中查看到具体的代码讲解,WAAPI Database 在文中作为实例分析了代码的结构设计。
如果认可我和李 AA 老师的代码,请不要在 GitHub 吝啬您的 Star!

2.3. GUI 原型设计

2.4. GUI 代码实现

1
2


3.1. 布局构建

1
2


3.2. 程序连接

1
2


3.3. 执行效果

接下来讲什么?

文章作者: 溪夜
文章链接: http://xiye.art/2021/01/30/PySimpleGUI 第三篇/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 溪夜的音频博客